package org.fcrepo.test.api;
import static junit.framework.Assert.assertTrue;
import java.io.File;
import java.io.FileInputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.apache.commons.io.IOUtils;
import org.fcrepo.common.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public class ValidatorHelper {
private static final Logger LOGGER =
LoggerFactory.getLogger(ValidatorHelper.class);
/**
* Get all local filenames that correspond to declared schemas. Validation
* does not work with a "union of all schemata" schema. This was an attempt
* create the most minimal set of schema by traversing schemaLocation and
* <include> within the xsd files. Ultimately, this was a dead end, because
* of a bug in Xerces: https://issues.apache.org/jira/browse/XERCESJ-1130
* schemaLocation typically points to some http resource. For offline tests,
* we want to use the local copy of that resource (since we know we have
* them). Thus, for all declared and included schemas, produce a list of
* local filenames.
*
* @param xml
* blob of xml that may contain schemaLocation
* @return List of all local files corresponding to declared schemas
* @throws Exception
*/
public List<File> getSchemaFiles(String xml, List<File> state)
throws Exception {
File schemaDir =
new File(Constants.FEDORA_HOME, "server" + File.separator
+ "xsd");
/* Get local copies of any declared schema */
ArrayList<File> result = new ArrayList<File>();
Pattern p = Pattern.compile("schemaLocation=\"(.+?)\"");
Matcher m = p.matcher(xml);
while (m.find()) {
String[] content = m.group(1).split("\\s+");
for (String frag : content) {
if (frag.contains(".xsd")) {
String[] paths = frag.split("/");
File newSchema =
new File(schemaDir, paths[paths.length - 1]);
if (state == null || !state.contains(newSchema)) {
result.add(newSchema);
}
}
}
}
/* For each declared schema, and get any <include> schemas from them */
ArrayList<File> included = new ArrayList<File>();
for (File f : result) {
xml = IOUtils.toString(new FileInputStream(f));
included.addAll(getSchemaFiles(xml, result));
}
result.addAll(included);
return result;
}
/**
* Validate XML document supplied as a string.
* <p>
* Validates against the local copy of the XML schema specified as
* schemaLocation in document
*</p>
*
* @param xml
* @throws Exception
*/
public void offlineValidate(String url, String xml, List<File> schemas)
throws Exception {
SchemaFactory sf =
SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
LOGGER.info(sf.getClass().getName());
ArrayList<Source> schemata = new ArrayList<Source>();
for (File schemaFile : schemas) {
schemata.add(new StreamSource(schemaFile));
}
Schema schema;
try {
schema = sf.newSchema(schemata.toArray(new Source[0]));
} catch (SAXException e) {
throw new RuntimeException("Could not parse schema "
+ schemas.toString(), e);
}
Validator v = schema.newValidator();
StringBuilder errors = new StringBuilder();
v.setErrorHandler(new ValidatorErrorHandler(errors));
v.validate(new StreamSource(new StringReader(xml)));
assertTrue("Offline validation failed for " + url + ". Errors: "
+ errors.toString() + "\n xml:\n" + xml, 0 == errors.length());
}
/**
* Validate XML document supplied as a string. Validates against XML schema
* specified as schemaLocation in document
*
* @param xml
* @throws Exception
*/
public void onlineValidate(String url, String xml) throws Exception {
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setValidating(true);
factory.setNamespaceAware(true);
SAXParser parser;
parser = factory.newSAXParser();
parser
.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
StringBuilder errors = new StringBuilder();
XMLReader reader = parser.getXMLReader();
reader.setEntityResolver(new ValidatorEntityResolver());
reader.setErrorHandler(new ValidatorErrorHandler(errors));
reader.parse(new InputSource(new StringReader(xml)));
if (errors.length() > 0) LOGGER.warn(xml);
assertTrue("Online Validation failed for " + url + ". Errors: "
+ errors.toString(), 0 == errors.length());
}
}